/*
Project:	CoreEngine
Author:		Dino Bojadzhievski
File:		Matrix4

Defines a four-dimensional matrix.
Non-generic, non-polymorphic.
Extends nothing.

Supports minor and cofactor extraction.
*/

#pragma once

#include "../CoreTypes.h"
#include "Matrix3.h"
#include "Vector4.h"
#include <assert.h>

namespace CoreMath
{
	struct Matrix4
	{
		/* Data. */

		real value[4][4];

		/* Constructors. */

		Matrix4()
		{
			this->Identity();
		}

		Matrix4(real fromRawValue[4][4])
		{
			for (byte i = 0; i < 4; i++)
			for (byte j = 0; j < 4; j++)
				this->value[i][j] = fromRawValue[i][j];
		}


		Matrix4(const Matrix2& toCopy)
		{
			for (byte i = 0; i < 4; i++)
			for (byte j = 0; j < 4; j++)
				this->value[i][j] = toCopy.value[i][j];
		}

		/* Operators. */

		inline Matrix4 operator=(const Matrix4& toAssign)
		{
			for (byte i = 0; i < 4; i++)
			for (byte j = 0; j < 4; j++)
				this->value[i][j] = toAssign.value[i][j];
			return *this;
		}

		inline bool operator==(const Matrix4& toCompare) const
		{
			for (byte i = 0; i < 4; i++)
			for (byte j = 0; j < 4; j++)
			if (this->value[i][j] != toCompare.value[i][j])
				return false;
			return true;
		}

		inline bool operator!=(const Matrix4& toCompare) const
		{
			return !(*this == toCompare);
		}

		inline Matrix4 operator+(const Matrix4& toAdd) const
		{
			Matrix4 m_RetVal;
			for (byte i = 0; i < 4; i++)
			for (byte j = 0; j < 4; j++)
				m_RetVal.value[i][j] = this->value[i][j] + toAdd.value[i][j];

			return m_RetVal;
		}

		inline Matrix4 operator-(const Matrix4& toSubtract) const
		{
			Matrix4 m_RetVal;
			for (byte i = 0; i < 4; i++)
			for (byte j = 0; j < 4; j++)
				m_RetVal.value[i][j] = this->value[i][j] - toSubtract.value[i][j];
			return m_RetVal;
		}

		inline Matrix4 operator*(const real& multiplier) const
		{
			Matrix4 m_RetVal;
			for (byte i = 0; i < 4; i++)
			for (byte j = 0; j < 4; j++)
				m_RetVal.value[i][j] = this->value[i][j] * multiplier;
			return m_RetVal;
		}

		inline Vector4 operator*(const Vector4& multiplier) const
		{
			return this->MultiplyByColumnVector(multiplier);
		}

		inline Matrix4 operator*(const Matrix4& multiplier) const
		{
			real rawValue[4][4] = 
			{ 
				0, 0, 0, 0, 
				0, 0, 0, 0, 
				0, 0, 0, 0, 
				0, 0, 0, 0 
			};

			Matrix4 m_RetVal(rawValue);

			for (byte i = 0; i < 4; i++)
			for (byte j = 0; j < 4; j++)
			for (byte k = 0; k < 4; k++)
				m_RetVal.value[i][j] = m_RetVal.value[i][j] + (this->value[i][k] * multiplier.value[k][j]);

			return m_RetVal;
		}

		inline Matrix4 operator*=(const real& multiplier)
		{
			*this = *this * multiplier;
			return *this;
		}

		inline Matrix4 operator*=(const Matrix4& multiplier)
		{
			*this = *this * multiplier;
			return *this;
		}

		inline Matrix4 operator-(void) const
		{
			real determinant = this->Determinant();
			if (!determinant) return *this; // Non-invertible matrix detected.
			Matrix4 cofactors = this->Cofactors();
			cofactors.Transpose();
			return cofactors * (1 / determinant);
		}

		/* Common matrix operations implementations.*/

		void Identity(void)
		{
			for (byte i = 0; i < 4; i++)
			for (byte j = 0; j < 4; j++)
			if (i == j)
				this->value[i][j] = 1;
			else
				this->value[i][j] = 0;
		}

		void Transpose(void)
		{
			Matrix4 m_RetVal;
			for (byte i = 0; i < 4; i++)
			for (byte j = 0; j < 4; j++)
				m_RetVal.value[i][j] = this->value[j][i];

			for (byte i = 0; i < 4; i++)
			for (byte j = 0; j < 4; j++)
				this->value[i][j] = m_RetVal.value[i][j];
		}

		inline real Trace(void) const
		{
			real trace = 0;
			for (byte i = 0; i < 4; i++)
				trace += this->value[i][i];
			return trace;
		}

		real Determinant(void) const
		{
			real determinant = 0;
			for (byte i = 0; i < 4; i++)
				for (byte j = 0; j < 4; j++)
				{
					real cVal = Minor(i, j).Determinant();
					if (i + j % 2 != 0)
						cVal *= -1;
					determinant += cVal;
				}
				return determinant;
		}

		Matrix3 Minor(const byte row, const byte column) const
		{
			Matrix3 m_RetVal;
			assert(row >= 0 && row < 4);
			assert(column >= 0 && column < 4);

			byte colCount = 0;
			byte rowCount = 0;

			for (int i = 0; i < 4; i++)
			{
				if (i != row)
				{
					colCount = 0;
					for (int j = 0; j < 4; j++)
					{
						if (j != column)
						{
							m_RetVal.value[rowCount][colCount] = this->value[i][j];
							colCount++;
						}
					}
					rowCount++;
				}
			}
			return m_RetVal;
		}

		Matrix4  Minors(void) const
		{
			Matrix4 m_RetVal;
			for (byte i = 0; i < 4; i++)
			for (byte j = 0; j < 4; j++)
				m_RetVal.value[i][j] = this->Minor(i, j).Determinant();
			return m_RetVal;
		}

		Matrix4 Cofactors(void) const
		{
			Matrix4 m_RetVal = this->Minors();
			for (byte i = 0; i < 4; i++)
			for (byte j = 0; j < 4; j++)
			if (i + j != 0)
				m_RetVal.value[i][j] *= -1;
		}

		inline Vector4 MultiplyByColumnVector(const Vector4& multiplier) const
		{
			real i, j, k, v;
			i = this->value[0][0] * multiplier.i + this->value[0][1] * multiplier.j + this->value[0][2] * multiplier.k + this->value[0][3] * multiplier.v;
			j = this->value[1][0] * multiplier.i + this->value[1][1] * multiplier.j + this->value[1][2] * multiplier.k + this->value[1][3] * multiplier.v;
			k = this->value[2][0] * multiplier.i + this->value[2][1] * multiplier.j + this->value[2][2] * multiplier.k + this->value[2][3] * multiplier.v;
			v = this->value[3][0] * multiplier.i + this->value[3][1] * multiplier.j + this->value[3][2] * multiplier.k + this->value[3][3] * multiplier.v;
			return Vector4(i, j, k, v);
		}
	};
}